﻿using JamesZhao.Engine.Graphics.Effects;
using JamesZhao.Engine.Graphics.Geometry;
using JamesZhao.Engine.Graphics.Surfaces;
using JamesZhao.Engine.Graphics.Visuals;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using Matrix = SlimMath.Matrix;
using Resource = SlimDX.Direct3D11.Resource;

namespace JamesZhao.Engine.Graphics.RenderStages
{
    internal class ShadowedLightsStage : RenderStage
    {
        private EffectResourceVariable _varAlbedoTexture;
        private EffectResourceVariable _varDepthTexture;
        private EffectVectorVariable _varLightPosIntensity;
        private EffectResourceVariable _varNormalTexture;
        private EffectResourceVariable _varShadowDepthTexture;
        private EffectMatrixVariable _varViewToLightProjection;
        private EffectMatrixVariable _varViewToLightView;
        private EffectVectorVariable _vargBufferSize;

        public ShadowedLightsStage(GraphicsDevice device)
        {
            GenShadowDepthEffect = new GpuProgram(device, "Shaders/GenShadowDepth.fx");
            DeferredWithShadowEffect = new GpuProgram(device, "Shaders/DeferredWithShadow.fx");

            var quadGeom =
                new GeometryBuffer(device);
            quadGeom.InitializeFrom(MeshData.LoadFullScreenQuad());
            FullScreenQuad = new MeshDrawable
                                 {
                                     GeometryBuffer = quadGeom
                                 };
        }

        protected MeshDrawable FullScreenQuad { get; set; }

        public GpuProgram DeferredWithShadowEffect { get; set; }

        public GpuProgram GenShadowDepthEffect { get; set; }

        public override string Name
        {
            get { return "Shadowed Lighting"; }
        }


        public GBuffer GBuffer { get; set; }
        public ColorSurface MsaaFloatDepthTexture { get; set; }
        public DepthSurface NativeDepthTexture { get; set; }

        public ColorSurface FloatDepthTexture { get; set; }

        public ColorSurface ColorSurface { get; set; }

        protected override void OnRender(GraphicsDevice device, SwapChain swapChain, VisualsList visualsList,
                                         Camera camera)
        {
            DeferredWithShadowEffect.ScalarVariables["zFar"].Set(camera.Lens.Far);
            GenShadowDepthEffect.ScalarVariables["zFar"].Set(camera.Lens.Far);
            if (_varNormalTexture == null)
            {
                _varNormalTexture = DeferredWithShadowEffect.ResourceVariables["normalTexture"];
                _varDepthTexture = DeferredWithShadowEffect.ResourceVariables["depthTexture"];
                _varAlbedoTexture = DeferredWithShadowEffect.ResourceVariables["albedoTexture"];
                _varShadowDepthTexture = DeferredWithShadowEffect.ResourceVariables["shadowDepthTexture"];
                _vargBufferSize = DeferredWithShadowEffect.VectorVariables["gBufferSize"];
                _varLightPosIntensity = DeferredWithShadowEffect.VectorVariables["light"];
                _varViewToLightProjection = DeferredWithShadowEffect.MatrixVariables["ViewToLightProjection"];
                _varViewToLightView = DeferredWithShadowEffect.MatrixVariables["ViewToLightView"];
            }

            //the old blend state
            BlendState oldBlendState = device.ImmediateContext.OutputMerger.BlendState;
            //device.ImmediateContext.ClearRenderTargetView(FinalColorSurface.TargetView, new Color4(0, 0, 0, 0));
            for (int index = 0; index < visualsList.Lights.Count; index++)
            {
                Light light = visualsList.Lights[index];
                if (light.IsShadowed)
                {
                    Performance.BeginEvent(new Color4(1, 0, 1, 0), "Light " + index);

                    Performance.BeginEvent(new Color4(1, 0, 1, 0), "Render Depth");
                    //render scene from light dir

                    RasterizerState oldRasterizerState = device.ImmediateContext.Rasterizer.State;
                    device.ImmediateContext.ClearDepthStencilView(NativeDepthTexture.DepthView,
                                                                  DepthStencilClearFlags.Depth,
                                                                  1, 0);
                    device.ImmediateContext.ClearRenderTargetView(MsaaFloatDepthTexture.TargetView,
                                                                  new Color4(0, 0, 0, 0));

                    device.ImmediateContext.OutputMerger.SetTargets(NativeDepthTexture.DepthView,
                                                                    MsaaFloatDepthTexture.TargetView);

                    var lightViewport = new Viewport(0, 0, MsaaFloatDepthTexture.ActualSize.Width,
                                                     MsaaFloatDepthTexture.ActualSize.Height);
                    device.ApplyTemporaryViewport(lightViewport);
                    foreach (Drawable renderable in visualsList.Drawables)
                    {
                        renderable.RenderWithGpuProgram(device, light.LightCamera, GenShadowDepthEffect);
                    }
                    device.RevertViewport();
                    device.ImmediateContext.Rasterizer.State = oldRasterizerState;
                    Performance.EndEvent();
                    //render deferred w/ shadow


                    //set deferred shading vars
                    _varNormalTexture.SetResource(GBuffer.Normal.ResourceView);
                    _varDepthTexture.SetResource(GBuffer.Depth.ResourceView);
                    _varAlbedoTexture.SetResource(GBuffer.Albedo.ResourceView);
                    _vargBufferSize.Set(new Vector2(GBuffer.Normal.ActualSize.Width, GBuffer.Normal.ActualSize.Height));

                    device.ImmediateContext.OutputMerger.SetTargets(ColorSurface.TargetView);


                    Performance.BeginEvent(new Color4(1, 0, 1, 0), "Render Deferred");
                    device.ImmediateContext.ResolveSubresource(MsaaFloatDepthTexture.Texture,
                                                               Resource.CalculateSubresourceIndex(0, 0, 1),
                                                               FloatDepthTexture.Texture,
                                                               Resource.CalculateSubresourceIndex(0, 0, 1),
                                                               Format.R32G32_Float);
                    //redundant... just for debugging
//                    device.ImmediateContext.ClearDepthStencilView(FinalDepthSurface.DepthView,
//                                                                  DepthStencilClearFlags.Depth,
//                                                                  1, 0);

                    var pos = new Vector4(light.LightCamera.Position, 1);
                    Vector4 vPos = Vector4.Transform(pos, camera.ViewMatrix);


                    _varLightPosIntensity.Set(new Vector4(new Vector3(vPos.X, vPos.Y, vPos.Z), light.Intensity));

                    Matrix viewToWorld = camera.ViewMatrix;
                    viewToWorld.Invert();
                    Matrix viewToLightView = viewToWorld*light.LightCamera.ViewMatrix;
                    Matrix viewToLightProjection = viewToLightView*light.LightCamera.Lens.ProjectionMatrix;

                    _varViewToLightView.SetMatrix(viewToLightView);
                    _varViewToLightProjection.SetMatrix(viewToLightProjection);

                    device.ImmediateContext.GenerateMips(FloatDepthTexture.ResourceView);
                    _varShadowDepthTexture.SetResource(FloatDepthTexture.ResourceView);


                    FullScreenQuad.RenderWithGpuProgram(device, camera, DeferredWithShadowEffect);

                    Performance.EndEvent();
                    Performance.EndEvent();
                }
            }
            device.ImmediateContext.OutputMerger.BlendState = oldBlendState;
        }
    }
}